﻿using System;
using System.Collections;
using EQMCHelpers;
using EQMCSharpPayoffs;
namespace userPayoff
{
    public class userFunctions : EQMCBase
    {
        /* declare variables/functions */
        int nReturns, stkIndex;
        double p, R, cap, floor, weightSum, targetVol, low, high, perf, annFac, alpha, fundingRate;
        double[] weights, bsk, yearFractions;
        /* initialise variables */
        public void initData()
        {
            nReturns = getInt("nReturns", -1);
            annFac = getDouble("annFac", 254.0);
            weights = new double[nReturns]; bsk = new double[nObs + 1]; yearFractions = new double[nObs + 1];
            alpha = getDouble("weightPower", 1.0 - 3.0 / (double)nReturns);
            weightSum = alpha;
            weights[0] = weightSum;
            for (int t = 1; t < nReturns; ++t)
            {
                weights[t] = alpha * weights[t - 1];
                weightSum += weights[t];
            }
            fundingRate = getDouble("fundingRate", 0.0);
            string dcb = getString("daycountBasis", "ACT365");
            for (int t = 1; t <= nObs; ++t)
            {
                yearFractions[t] = fundingRate != 0 ? yearFraction(t - 1, t, dcb) : 0.0;
            }
            targetVol = getDouble("target", 0.0);
            cap = getDouble("cap", 1000.0);
            floor = getDouble("floor", 0.0);
            if (Math.Abs(floor) > 1e-8) { throw new Exception("Error: floor must be set to 0.0, not " + floor + ", use strikeFactor instead"); }
            low = getDouble("lowerLimit", 1.0);
            high = getDouble("upperLimit", 1.0);
            p = getDouble("participation", 1.0);
            R = getDouble("redemptionCash", 0.0);
            for (int t = 1; t <= nObs; ++t)
            {
                if (isEvent(t, SCH1))
                {
                    stkIndex = t - 1;
                    if (stkIndex < nReturns) { throw new Exception("Error: sch1 is too early, not enough obs for 1st realised vol computation"); }
                }
            }
        }
        /* auxiliary function */
        public double option(double S)
        {
            if (derivativeType == PUT) { return Math.Max(strikeFactor - S, 0.0); }
            if (derivativeType == CALL) { return Math.Max(S - strikeFactor, 0.0); }
            if (derivativeType == FWD) { return (S - strikeFactor); }
            throw new Exception("Error: Invalid derivativeType!");
        }
        /* define payout */
        public void calcCorePayoffBi(int trial)
        {
            if (!isEvent(nObs, SCH2)) { throw new Exception("Error: sch2 must be present on expDate"); }
            bool isStruck = false;
            double currSum = 0.0;
            double currTW;
            double currSpot = 1.0;
            double currExp = 0.0; bsk[0] = 0.0;
            for (int t = 0; t <= nObs; ++t)
            {
                bsk[t] = 0;
                for (int u = 0; u < nUnds; ++u)
                {
                    bsk[t] += weight(u) * NS(trial, u, t) / NS(trial, u, stkIndex + 1);
                }
            }
            for (int t = 1; t <= nObs; t++)
            {
                if (isStruck)
                {
                    currSpot = currSpot * (1.0 + currExp * (bsk[t] / bsk[t - 1] - 1.0 - yearFractions[t] * fundingRate));
                    currTW = Math.Min(cap, targetVol / Math.Sqrt(currSum * annFac / weightSum));
                    if (currTW < low * currExp || currTW > high * currExp)
                        currExp = currTW;
                    perf = Math.Log(bsk[t - nReturns] / bsk[t - nReturns - 1]);
                    currSum -= perf * perf * weights[nReturns - 1];
                    currSum *= weights[0];
                    perf = Math.Log(bsk[t] / bsk[t - 1]);
                    currSum += weights[0] * perf * perf;
                }
                if (t == stkIndex)
                {
                    for (int tt = 0; tt < nReturns; ++tt)
                    {
                        perf = Math.Log(bsk[t - tt] / bsk[t - tt - 1]);
                        currSum += weights[tt] * perf * perf;
                    }
                    isStruck = true;
                }
                if (isEvent(t, SCH2))
                {
                    finalPayoff += currSpot;
                }
            }
            finalPayoff /= nSch2s;
            finalPayoff = R + p * option(finalPayoff);
        }
    }
}

